#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：idi_mes 
@File    ：wip_check.py
@IDE     ：PyCharm 
@Author  ：孔令伟
@Date    ：9/23/24 10:45 AM 
"""
import json
from typing import Dict, List, Tuple, Union
from mg_app_framework import MesCode
from common.basic_mongo import get_retrospect_collection
from common.basic_redis import get_redis_conn
from common.constant import RetrospectType
from prod_ctrl_service.tool.tool import (check_basic_param, get_feeding_material_code_num_map,
                                         aggregate_unqualified_records, check_repeat_in_or_out)


async def wip_check(params: Dict[str, Union[str, List[str]]]) -> Dict[str, Union[str, Dict]]:
    """
    组盘时扫描正极电芯码提交给MES进行电芯码校验(IF026)
    :param params:{
                "call_id":"JQ00386",  #接口调用方认证ID, string
                "trace_id":"8941d16b-0581-4e68-8940-a651afff4282",  #接口调用ID, string
                "upperpc_code": "UP011",    #上位机编码, string
                "user_code":"Y001",  #员工编码, string
                "equip_code":"ZP001",   #设备编码, string
                "process_code":"2050",   # 工序编码, string, 必输
                "cell_list":["WW202408140001","WW202408140002"]  #电芯码列表,Y,需要校验的电芯码列表
             }
    :return:{
                "code": "success",   #接口应答状态代码, success：成功; fail：失败, string
                "data": {},  # 接口返回结果描述, json数组, 本接口不需要返回数据，返回null
                "info": "OK"   #接口返回信息描述, 如果为fail, 则为fail的异常描述信息, string
             }
    """
    error_msg = ""
    # '接口调用方认证ID/接口调用ID/上位机编码/员工编码/设备编码'是每一个接口都有且必填的入参，检查这些参数是否填写
    basic_param_check_result, basic_param_check_message = check_basic_param(params=params)
    if not basic_param_check_result:
        error_msg += basic_param_check_message
    wip_no_list = params.get("cell_list", [])
    process_code = params.get("process_code")
    equip_code = params.get("equip_code")
    if not wip_no_list:
        error_msg += "传入参数'电芯码列表(cell_list)'未填写;"
    # 对传入的码值进行不合格品记录校验;  re_input_wip : 复投的码值
    unqualified_check_msg, re_input_wip = await aggregate_unqualified_records(wip_no_list, process_code)
    if unqualified_check_msg:
        error_msg += unqualified_check_msg
        result = {
            "code": MesCode.fail,  # 接口应答状态代码, success：成功; fail：失败, string
            "data": {},  # 接口返回结果描述, json数组, 本接口不需要返回数据，返回null
            "info": error_msg,  # 接口返回信息描述, 如果为fail, 则为fail的异常描述信息, string
        }
        return result
    # bom信息
    # redis get bom
    rd_conn = get_redis_conn()
    ts_key = f"workorder_mgm~{equip_code}"
    bom_info = await rd_conn.get(ts_key)
    bom_info = json.loads(bom_info)
    workorder_status = bom_info.get("workorder_status")
    # 通过bom信息校验当前工序工单状态
    if workorder_status != "on_work":
        error_msg += f"当前工序无生产中工单;"
    pre_procedure_code: list = bom_info.get("pre_procedure_code", [])

    # 当前工序重码校验(一些码值本次进站操作属于复投，不需要校验是否重复进站)
    qr_code_for_check_repeat_in: list = list(set(wip_no_list) - set(re_input_wip))
    qr_code_has_used = await check_repeat_in_or_out(
        process_code, qr_code_for_check_repeat_in, RetrospectType.PRODUCT_IN
    )
    if qr_code_has_used:
        error_msg += f"下列在制品单件码重复进站:{qr_code_has_used};"

    # no_last_retros_code : 在前工序上没有追溯数据的电芯码列表
    # retrospect_records : 每一个电芯码与它在前工序上的最新一条追溯数据的映射关系
    retrospect_records, no_last_retros_code = await get_last_retrospect(
        last_procedure_code_list=pre_procedure_code, qr_code_list=wip_no_list
    )
    # 前工序没有产出追溯信息校验
    if no_last_retros_code:
        error_msg += f"下列电芯码：不存在追溯数据或最新出站数据不是前工序出站：{no_last_retros_code};"
    # 校验电芯码在前道工序生产的物料信息是否与当前工序bom信息中投入的在制品信息是否一致；
    # 查询前一道工序追溯数据的check_result是否合格
    # BOM齐套性校验
    # 原材料批次码的投料数量是否满足bom投入物料中各批次码的扣料数量
    check_result, bom_check_result_msg = await check_material(
        retrospect_records=retrospect_records,
        bom_info=bom_info,
        process_code=process_code,
    )
    if not check_result:
        error_msg += bom_check_result_msg

    # todo Marking校验, marking相关功能未开发，等待开发后补充marking校验功能

    if error_msg:
        result = {
            "code": MesCode.fail,  # 接口应答状态代码, success：成功; fail：失败, string
            "data": {},  # 接口返回结果描述, json数组, 本接口不需要返回数据，返回null
            "info": error_msg,  # 接口返回信息描述, 如果为fail, 则为fail的异常描述信息, string
        }
    else:
        result = {
            "code": MesCode.success,  # 接口应答状态代码, success：成功; fail：失败, string
            "data": {},  # 接口返回结果描述, json数组, 本接口不需要返回数据，返回null
            "info": f"OK",  # 接口返回信息描述, 如果为fail, 则为fail的异常描述信息, string
        }

    return result


async def get_last_retrospect(
        last_procedure_code_list: list, qr_code_list: list
) -> Tuple[Dict[str, Dict], List]:
    """
    获取入参中在制品单间码在追溯表中前一道工序上的产出信息
    :param last_procedure_code_list:
    :param qr_code_list:
    :return:
    """
    qr_code_map = {rec: rec for rec in qr_code_list}
    pipeline = [
        {
            "$match": {
                "qr_code": {"$in": qr_code_list},
                # "procedure_code": {"$in": last_procedure_code_list},
                # "op_type": RetrospectType.PRODUCT_OUT,
                "is_delete": False,
            }
        },
        {"$sort": {"op_time": -1}},
        {"$group": {"_id": "$qr_code", "latestDocument": {"$first": "$$ROOT"}}},
    ]
    collection = get_retrospect_collection()
    result = {}
    async for rec in collection.aggregate(pipeline):
        qr_code = rec["_id"]
        document = rec["latestDocument"]
        if document["procedure_code"] in last_procedure_code_list and document["op_type"] == RetrospectType.PRODUCT_OUT:
            result.update({qr_code: document})
            qr_code_map.pop(qr_code)  # 如果前道工序有该条码的产出(out)追溯信息，则从字典中pop该条码，最终字典中剩余的就是前道工序没有产出信息的条码

    return result, list(qr_code_map.keys())


async def check_material(retrospect_records: Dict[str, Dict], bom_info: dict, process_code: str) -> Tuple[bool, str]:
    """
    校验在制品单件码在前道工序生产的物料信息是否与当前工序bom信息中投入的在制品信息是否一致；
    查询前一道工序追溯数据的check_result是否合格;
    BOM齐套性校验;
    :param process_code:
    :param retrospect_records:
    :param bom_info:
    :return:
    """
    target_material_code_list = []  # 获取当前工序bom信息中分类是在制品（半成品）的投入物料信息
    target_material_code_list_copy = {}  # 获取当前工序bom信息中分类是在制品（半成品）的投入物料信息
    target_material_code_quota_map = {}  # 获取当前工序BOM信息中投入物料分类是批次码的物料以及它的扣料数量
    if bom_info and bom_info.get("bom_info"):
        for rec in bom_info["bom_info"].get("product_in", []):
            if rec.get("material_type") == "semi_finished_product":
                target_material_code_list.append(rec.get("material_code"))
                target_material_code_list_copy.update(
                    {rec.get("material_code"): rec.get("material_code")}
                )
            if rec.get("code_type") == "batch_code":
                target_material_code_quota_map.update(
                    {rec.get("material_code"): rec.get("material_quota")}
                )

    # 原料码值与投料数量的映射关系
    material_code_list: list = list(target_material_code_quota_map.keys())
    material_code_num_map: dict = await get_feeding_material_code_num_map(
        code_list=material_code_list, process_code=process_code
    )
    no_bom_material_info = []
    # no_check_result_info = []  # 取消对码值前工序产出是否是NG品的校验
    no_target_num_info = []
    for qr_code, document in retrospect_records.items():
        material_code = document.get("material_code")
        # check_result = document.get("check_result")
        # 校验在制品单间码前道工序产出是否在本工序bom信息投入物料的在制品（半成品）中
        if material_code not in target_material_code_list:
            no_bom_material_info.append(qr_code)
        else:
            # 如果条码前工序产出是本工序bom投入料的在制品，则从‘target_material_code_list_copy’将该物料信息pop，
            # ‘target_material_code_list_copy’剩余的物料信息即为本工序bom需要的投入在制品物料但是入参条码的前工序产出物料中没有，
            # 即本工序投入物料不完整，无法开工
            if material_code in target_material_code_list_copy:
                # 可能多个在制品单件码对应同一个在制品，那么在校验第一个的时候，已经从  里把这个在制品编码pop了，
                # 第二个及之后的码值时，需要判断下是否还在，如果已经pop了，就不需要重复操作了；
                # 上一层if已经判断了码值是不是bom所需在制品，能走到这一层判断的都是bom需要的在制品。
                target_material_code_list_copy.pop(material_code)
            else:
                pass

        # 校验在制品单间码前道工序产出是否合格
        # if check_result == "不合格":
        #     no_check_result_info.append(qr_code)

    # 核对BOM信息中批次码的扣料数量与各物料当前系统中的投料数量的关系，如果扣料>投料，需要提示投料数量不足；
    for k, v in target_material_code_quota_map.items():
        if v > material_code_num_map.get(k, 0):
            no_target_num_info.append(k)

    # 用result来判断是不是必须提示的错误信息；投料数量由于允许扣减为负数，
    # 所以当这一项不满足校验时，仍然可以进站，不是强制报错，返回信息中提示即可
    result, error_msg = True, ""
    if no_bom_material_info:
        result = False
        error_msg += f"下列单件码进站物料与BOM所需半成品不一致:{no_bom_material_info};"
    # if no_check_result_info:
    #     result = False
    #     error_msg += f"下列单件码进站物料为NG品:{no_check_result_info};"
    # if target_material_code_list_copy:
    #     result = False
    #     error_msg += f"BOM齐套性校验未通过, 本工序投入原料不齐套;"
    # if no_target_num_info:
    #     result = False
    #     error_msg += f"下列物料的投料数量少于工序BOM扣减数量:{no_target_num_info};"

    return result, error_msg
